/*!
* \file main.c
* \brief main program for atmega8 RoboBIOS
*
*  URL: http://robotika.cz/
*  
*  Revision: 1.2
*  Date: 2005/11/01
*/

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>

// config for Tiny25
#define LED_PORT PORTB
#define LED_DDR DDRB
#define RED PB4
#define GREEN PB0
#define BLUE PB2
#define COMMON PB1

#define LED_OFF(pin) LED_PORT &= ~_BV(pin)
#define LED_ON(pin) LED_PORT |= _BV(pin)

#define SECONDS_TO_UPTIME(sec) (25*sec)

#define VOLTAGE_DROP_MV 600

// this is for 8k2/3k3 divider
#define MILLIVOLTS_TO_AD(mv) ((mv-VOLTAGE_DROP_MV)/68)

// here you can configure time in each mode (DO NOT use decimal points, use fractions instead)
#define VOLTMON_TIME (SECONDS_TO_UPTIME(8))
#define SWITCHIND_TIME (SECONDS_TO_UPTIME(1/2))

#define MEASUREMENT_COUNT 12

volatile uint8_t redValue = 0;
volatile uint8_t greenValue = 0;
volatile uint8_t blueValue = 0;
volatile uint8_t blinkValue = 0;
volatile uint8_t blinkIsOn = 0;
volatile uint8_t blinkTicks = 0;

volatile uint8_t tickValue = 0;

volatile uint8_t upTime = 0;

volatile uint8_t measurementIndex;
volatile uint8_t measurementArray[MEASUREMENT_COUNT];

uint8_t getAveragedValue() {
	uint8_t i;
	uint16_t v = 0;

	for (i = 0; i < MEASUREMENT_COUNT; i++)
		v += measurementArray[i];
	return (uint8_t)(v / MEASUREMENT_COUNT);
}

uint8_t blendColor(uint8_t adCurrent, uint8_t adStart, uint8_t valueStart, uint8_t adEnd, uint8_t valueEnd) {
	
	if (adCurrent <= adStart)
		return valueStart;
	else if (adCurrent >= adEnd)
		return valueEnd;

	if (valueStart < valueEnd)
		return valueStart +  (adCurrent-adStart) * (valueEnd-valueStart) / (adEnd-adStart);
	else
		return valueStart -  (adCurrent-adStart)  * (valueStart-valueEnd) / (adEnd-adStart);
}

ISR(ADC_vect) {
	cli();
	measurementArray[measurementIndex] = ADCH;
	measurementIndex = (measurementIndex + 1) % MEASUREMENT_COUNT;
	uint8_t adValue = getAveragedValue();
	sei();

	if (upTime < VOLTMON_TIME) { 
		// voltage monitor mode
		if (adValue <= MILLIVOLTS_TO_AD(10800)) {
			// red, fast flash
			redValue = 8;
			greenValue = 0;
			blueValue = 0;
			blinkValue = 0x11;
			OCR1A = 255;
		} else if (adValue <= MILLIVOLTS_TO_AD(11200)) {
			// red, slow flash
			redValue = 8;
			greenValue = 0;
			blueValue = 0;
			blinkValue = 0x44;
			OCR1A = 255;
		} else if (adValue <= MILLIVOLTS_TO_AD(12800)) {
			// blending red to green
			redValue =   blendColor(adValue,MILLIVOLTS_TO_AD(11200) , 8, MILLIVOLTS_TO_AD(12800), 0);
			greenValue = blendColor(adValue, MILLIVOLTS_TO_AD(11200), 0, MILLIVOLTS_TO_AD(12800), 8);
			blueValue = 0;
			blinkValue = 0x00;
			OCR1A = 255;
		} else if (adValue <= MILLIVOLTS_TO_AD(14400)) {
			// blending green to blue
			redValue = 0;
			greenValue = blendColor(adValue,MILLIVOLTS_TO_AD(12800), 8, MILLIVOLTS_TO_AD(14400), 0);
			blueValue = blendColor(adValue, MILLIVOLTS_TO_AD(12800), 0, MILLIVOLTS_TO_AD(14400), 8);
			blinkValue = 0x00;
			OCR1A = 255;
		} else if (adValue <= MILLIVOLTS_TO_AD(15000)) {
			// blue, steady
			redValue = 0;
			greenValue = 0;
			blueValue = 8;
			blinkValue = 0x00;
			OCR1A = 255;
		} else {
			// blue, fast flash
			redValue = 0;
			greenValue = 0;
			blueValue = 8;
			blinkValue = 0x11;
			OCR1A = 255;
		}

	} else if (upTime < (VOLTMON_TIME+SWITCHIND_TIME)) { 
		// switching state indication - white, steady
		redValue = greenValue = blueValue = 8;
		blinkValue = 0;
		OCR1A = 255;
	} else { 
		// charger monitor mode
		if (adValue <= MILLIVOLTS_TO_AD(10800)) {
			// red, fast flash
			redValue = 8;
			greenValue = 0;
			blueValue = 0;
			blinkValue = 0x11;
			OCR1A = 255;
		} else if (adValue <= MILLIVOLTS_TO_AD(11600)) {
			// red, slow flash
			redValue = 8;
			greenValue = 0;
			blueValue = 0;
			blinkValue = 0x44;
			OCR1A = 255;
		} else if (adValue <= MILLIVOLTS_TO_AD(12600)) {
			// red, steady
			redValue = 8;
			greenValue = 0;
			blueValue = 0;
			blinkValue = 0x00;
			OCR1A = 255;
		} else if (adValue <= MILLIVOLTS_TO_AD(13500)) {
			// blend red to green
			redValue =   blendColor(adValue,MILLIVOLTS_TO_AD(12600) , 8, MILLIVOLTS_TO_AD(13500), 0);
			greenValue = blendColor(adValue, MILLIVOLTS_TO_AD(12600), 0, MILLIVOLTS_TO_AD(13500), 8);
			blueValue = 0;
			blinkValue = 0x00;
			OCR1A = 255;
		} else if (adValue <= MILLIVOLTS_TO_AD(14800)) {
			// ok, led off
			redValue = 0;
			greenValue = 0;
			blueValue = 0;
			blinkValue = 0x00;
			OCR1A = 0;
		} else if (adValue <= MILLIVOLTS_TO_AD(15000)) {
			// blue, steady
			redValue = 0;
			greenValue = 0;
			blueValue = 8;
			blinkValue = 0x00;
			OCR1A = 255;
		} else {
			// blue, fast flash
			redValue = 0;
			greenValue = 0;
			blueValue = 8;
			blinkValue = 0x11;
			OCR1A = 255;
		}
	}
}

ISR(TIMER0_OVF_vect) {
	ADCSRA |= _BV(ADSC);	// start conversion

	if (upTime < 255)	// increase power-up time (in 1/24th of second, so max is approx 11s
		upTime++;
}

ISR(TIMER1_OVF_vect) {
	if (tickValue == 0) {

		if (blinkValue) {
			// blinking on
			if (blinkTicks == 0) {
				if (blinkIsOn) {
					blinkTicks = 5 * (blinkValue & 0x0f); // lower nibble contains time of darknesss
				} else {
					blinkTicks = 5 * (blinkValue >> 4);   // upper nibble contains time of light
				}
				blinkIsOn = !blinkIsOn;
			}
			blinkTicks--;
		}

		if (!blinkValue || blinkIsOn) {
			if (redValue > 0)
				LED_ON(RED);
			if (greenValue > 0)
				LED_ON(GREEN);
			if (blueValue > 0)
				LED_ON(BLUE);
		}
	}

	if (redValue == 0 || (blinkValue && !blinkIsOn) || (redValue < 8 && tickValue >= redValue))
		LED_OFF(RED);
	if (greenValue == 0 || (blinkValue && !blinkIsOn) || (greenValue < 8 && tickValue >= greenValue))
		LED_OFF(GREEN);
	if (blueValue == 0 || (blinkValue && !blinkIsOn) || (blueValue < 8 && tickValue >= blueValue))
		LED_OFF(BLUE);

	tickValue = (tickValue + 1) % 8;

}


//! initialize all modules
void init(void) {
	// configure timer 0 (time counter), overflows 25times per second
	TCCR0B = _BV(CS02); // normal operation / 1:256

	// configure timer 1 (PWM for LED_COMMON), overflows 48.9times per second
	TCCR1 = _BV(PWM1A) | _BV(COM1A1) | _BV(COM1A0) | _BV(CS12); // Enable PWM1A / set on match / 1:8

	// enable timer overflow interrupts
	TIMSK |= _BV(TOIE1) | _BV(TOIE0);

	// set all LED pins as outputs
	LED_DDR |= _BV(RED) | _BV(GREEN) | _BV(BLUE) | _BV(COMMON);

	// configure ADC
	ADMUX = _BV(ADLAR) | _BV(MUX1) | _BV(MUX0); // Vcc as reference / left aligned (8bit val in ADCH) / 3 as input (for Tiny25)
	DIDR0 |= _BV(ADC3D) | _BV(ADC2D) | _BV(ADC1D) | _BV(ADC0D); // disable digital input buffers (not needed)
	ADCSRA = _BV(ADEN) | _BV(ADSC) | _BV(ADIE) | _BV(ADPS2) | _BV(ADPS0); // enable, start conversion, enable interrupt, 1:32 (50kHz)
	

	TIMSK |= _BV(TOIE1) | _BV(TOIE0); // enable timer overflow interrupts
	sei(); // enable interrupts
}

//! main routine (program entry point)
int main(void)
{
	init();
	//LED_ON(RED);
	//LED_ON(GREEN);
	//LED_ON(BLUE);

	for(;;) {
		wdt_reset();
	}

}

